Rekurzija II


Vsote

1. podnaloga

Nekega dne je Ančka vsa obupana prosila brata Jureta, naj ji napiše program za domačo nalogo. Ta bi moral rekurzivno izračunati vsoto prvih n naravnih števil. A ker se je Juretu mudilo na vsakodnevni žur, je moral zelo hiteti in je zato v programu naredil nekaj napak. Jih najdeš?

   def vsota( n) :
       if n > 0 :
           return 1
       else :
           return vsota(n-1)

Uradna rešitev

def vsota( n) :
    if n <= 0 :
        return 0
    else :
        return n + vsota(n-1)

2. podnaloga

Jure je napisal kodo, ki s pomočjo rekurzije izračuna vsoto 1 + 1/2 + 1/3 + ... + 1/n. Žal mu je med sprehodom list s kodo padel v sneg in se je nekaj kode izbrisalo. Dopolni jo!

   def vsotaRec(n) :
      '''Izračun 1 + 1/2 + ... + 1 / n
         n je zagotovo naravno število
      '''
      if _______________ : # ustavitveni pogoj
            return ___________
      return ___________ + vsotaRec(_______) # rekurzivni klic

Uradna rešitev

def vsotaRec(n) :
   '''Izračun 1 + 1/2 + ... + 1 / n
      n je zagotovo naravno število
   '''
   if n == 1 : # ustavitveni pogoj
         return 1
   return 1 / n + vsotaRec(n - 1) # rekurzivni klic

Osnove rekurzije

1. podnaloga

Dana je rekurzivna funkcija `izpisObratniR', ki izpiše števila v obratnem vrstnem redu.

   def izpisObratniR(stevec) :
       if stevec <= 0 :
           return ""
       else :
           niz = "" + str(stevec) + ", " + izpisObratniR(stevec - 1)
           return niz

Kot vidiš, se na koncu izpiše odvečna vejica. Potrebujemo metodo, ki bi še vedno uporabljala rekurzijo, a ne bi izpisala dodatne vejice

Uradna rešitev

def izpisObratniR(stevec):
    '''Znebimo se odvečne , '''
    if stevec <= 0:
        return ""
    if stevec == 1:
        return "1"
    niz = "" + str(stevec) + ", " + izpisObratniR(stevec - 1)
    return niz

2. podnaloga

Sedaj na osnovi te metode sestavi izpisR(stevec), ki bo vrnila števila v "pravem" vrstnem redu. Še vedno pa uporabi rekurzijo in pazi, da ne bo odvečnih vejic!

Uradna rešitev

def izpisR(stevec):
    if stevec <= 0 :
        return ""
    if stevec == 1 :
        return "1"
    niz = izpisR(stevec - 1) + ', ' + str(stevec)
    return niz

Število enk

1. podnaloga

Sestavite rekurzivno funkcijo steviloEnk(n), ki prešteje, koliko je enk v dvojiškem zapisu nenegativnega celega števila n.

Primer: Število 52 je v dvojiškem zapisu enako 110100, torej so v njem tri enke. Če se ne spomniš postopka, si oglej ta posnetek

Uradna rešitev

def steviloEnk(n):
    '''Koliko enk je v dvojiškem zapisu števila n'''
    if n < 2: return n
    return n % 2 + steviloEnk(n // 2)

Barvanje ograje

Janezek mora za kazen prebarvati vrtno ograjo. Ta je sestavljena iz deščic širine 1 dm, ki so položene tesno skupaj ena ob drugi. Deščice so različnih višin, saj je ograjo izdelal Janezek sam iz odvečnih kosov, vsaka deščica pa se dotika tal, ki so ravna. Ograja izgleda nekako takole:

             +-+                             
     +-+     | |   +-+                             
     | | +-+-+ |   | |                           
   +-+ | | | | +-+-+ +-+                        
   | | +-+ | | | | | | |                        
   | | | | | | | | | | |                           
---------------------------

Janezek pozna višino vsake deščice, zato lahko ograjo opiše s seznamom, ki za vsako deščico vsebuje po eno število (višina v dm):

[2, 4, 1, 3, 3, 5, 2, 2, 4, 2]

Janezek ima čopič, ki je širok ravno 1 dm. Ograjo bo barval z navpičnimi in vodoravnimi potegi čopiča (nikoli ne vleče čopiča diagonalno). To počne tako, da se ob vsakem trenutku vse ščetine čopiča dotikajo ograje. Ponovno lahko potegne čopič tudi preko že pobarvanega dela ograje.

1. podnaloga

Janezek bi rad prebarval ograjo z minimalnim številom potez, da bi lahko čimprej odšel na obisk k Pepci. Sestavite funkcijo stevilo_potez(ograja), ki dobi seznam z opisom ograje in vrne minimalno število potez. Zgled:

>>> stevilo_potez([2, 2, 1, 2, 1])
3
>>> stevilo_potez([2, 2])
2
>>> stevilo_potez([5])
1

Uradna rešitev

def stevilo_potez(ograja):
    '''Koliko potez je potrebnih za prebarvanje ograje '''
    if len(ograja) == 0: # ni ograje
        return 0
    if len(ograja) == 1: # eno deščico prebarvamo naenkrat 
        return 1
    min_visina = min(ograja) 
    poteze = min_visina  # Vodoravne poteze.
    # sedaj barvamno od leve proti desni
    p = [] # kaj je ostalo še za prebarvati na levi strani 
    for i in range(len(ograja)): # pogledamo vse deščice
        if ograja[i] != min_visina: # deščica je višja, kot najnižja
            p.append(ograja[i] - min_visina) # pobarvati moramo še njen zgornji del
        else: # pobarvati moram vse levo, kar še nismo pobarvali prej
            poteze += stevilo_potez(p)  # Barvanje "podograje".
            p = [] # levo je sedaj pobarvano vse
    poteze += stevilo_potez(p) # ko smo na koncu, pobarvamo še preostale višje dele
    return min(poteze,
               len(ograja))  # Druga možnost so same navpične poteze.

Levenshteinova razdalja

1. podnaloga

Pri črkovalnikih (npr. Besana), optičnem prepoznavanju znakov in še marsikje nam zelo prav pride, če znamo za dva niza ugotoviti, kako podobna sta si, oziroma kakšna je razdalja med njima. Poznamo več različnih razdalj med nizi: Hammingovo, Damerau-Levenshteinovo, razdaljo največjega skupnega podniza ...

V MaFiRa Wikiju piše, da Levenshteinova razdalja (angl. Levenshtein distance ali edit distance) med dvema nizoma predstavlja minimalno število operacij, ki jih potrebujemo za preoblikovanje enega niza v drugega. Operacije so izbris, vstavljanje in zamenjava znakov. Dolžini nizov sta lahko poljubni. Niz $a$ # spremenimo v niz $b$ in pri tem uporabimo

Npr. 'banana' se od 'ananas' razlikuje za 2, ker potrebujemo vsaj dve operaciji, da iz banane naredimo ananas. Npr.

'banana' --(zbrišemo b)-> 'anana' --(vstavimo s)-> 'ananas'

Verjetno se ne boste čudili, da je razdalja med pojmoma 'matematika' in 'fizika' kar 7 ;-)

Sestavite funkcijo levenshteinovaRazdalja(a, b), ki kot argumenta prejme niza a in b. Funkcija naj vrne urejevalniško razdaljo med a in b.

>>> levenshteinovaRazdalja('banana', 'ananas')
2
>>> levenshteinovaRazdalja('potop', 'pokol')
2
>>> levenshteinovaRazdalja('matematika', 'fizika')
7

Nalogo rešite z rekurzijo, torej brez uporabe zanke for oziroma while.

Namig: niza si predstavljajte kot xA in yB. In potem preverite, katera od treh možnost za predelavo bo dala minimalno vrednost ... Pri tem ne pozabite še na možnost, da je x == y

Uradna rešitev

def levenshteinovaRazdalja(a, b):
    ''' Vrne Levenshteinovo razdaljo med nizoma a in b'''
    if a == "": # v prazen niz moramo vstaviti vse znake iz b
        return len(b)
    if b == "": # pobrisati moramo vse znake iz a
        return len(a)
    if a[0] == b[0]: # če bomo znak pustili in spreminjali preostanek
        dodatek = 0 
    else:
        dodatek = 1 # tu bomo znaka zamenjali 
    # lahko znak iz a pobrišemo in preostanek a spremenimo v b
    # ali a spremenimo v cel b razen prvega in na začetek vstavimo prvi znak b-ja
    # ali pa preostanek a spremenimo v preostanek b in upoštevamo še morebitno spremembo prvega znaka
    return min(1 + levenshteinovaRazdalja(a[1:], b), levenshteinovaRazdalja(a, b[1:]) + 1, dodatek + levenshteinovaRazdalja(a[1:], b[1:]))

Permutacije

Oznake idej se nanašajo na prosojnice permutacije v spletni učilnici FMF za predmet Programiranje 1 v š.l. 15/16

Če hočemo narediti permutacijo iz 0 elementov, vrnemo tako kot itertools [[]]

1. podnaloga

Na osnovi ideje 1 smo zgradili osnutek metode permutacijeV1(seznam). Dopolni ga do delujočega!

    def permutacijeV1(seznam):
       '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
           permutacijo elementov iz seznama '''
       ... manjka
       prviDel = seznam[:-1]
       zadnji = seznam[-1]
       permPrej = permutacijeV1(prviDel)
       # vstavi zadnji na vsa možna mesta
       rez = []
       for
          koncnaPer = permPrej z vstavljenim zadnjim
          rez.append(koncnaPer)
       ...
       return sorted(rez)

Uradna rešitev

def permutacijeV1(seznam):
    '''Vrne seznam seznamov, kjer posamezni seznam predstavlj
    permutacijo elementov iz seznama '''
    if seznam == []: # ni elementov, le prazna permutacija
        return [[]]
    if len(seznam) == 1: # en sam element - ena permutacija!
        return [[seznam[0]]]
    prviDel = seznam[:-1]
    zadnji = seznam[-1]
    permPrej = permutacijeV1(prviDel) 
    rez = []
    for posameznaP in permPrej:
        '''Iz te perm. naredimo len(seznam) novih '''
        for i in range(len(seznam)):
            novaP = posameznaP[:]
            novaP.insert(i, zadnji)
            rez.append(novaP)
    return sorted(rez)

2. podnaloga

Na osnovi ideje 2 smo zgradili osnutek metode permutacijeV2(seznam). Dopolni ga do delujočega!

    def permutacijeV2(seznam):
       '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
           permutacijo elementov iz seznama '''
       ... manjka
       vse = []
       for ind, elt in enumerate(seznam):
          # zgradimo vse permutacije iz ostalih
          vsePerm = permutacijeV2(seznam[:ind] + seznam[ind + 1:] )
          # in sedaj permutacijam dodamo na začetek element elt
          ...
       ...
       return sorted(vse)

Uradna rešitev

def permutacijeV2(seznam):
    '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
        permutacijo elementov iz seznama '''
    if seznam == []: # ni elementov, le prazna permutacija
        return [[]] 
    vse = []
    for ind, elt in enumerate(seznam):
       # zgradimo vse permutacije iz ostalih
       vsePerm = permutacijeV2(seznam[:ind] + seznam[ind + 1:] )
       # in sedaj permutacijam dodamo na začetek element elt
       for perm in vsePerm:
           vse.append([elt] + perm)
    return sorted(vse)

3. podnaloga

Na osnovi ideje 3 smo zgradili osnutek metode permutacijeV3(seznam). Dopolni ga do delujočega!

    def permutacijeV3(seznam, doSedaj = []):
       '''Vrne seznam vseh možnih permutacij iz elementov v elementi,
          ki imajo začetek enak permutaciji, ki so predstava doSedaj'''
       # Če smo že na koncu
           return [doSedaj]
       vse = []
       for elt in seznam:
          # če elt še lahko dodamo v doslej sestavljeno permutacijo
               # generiramo vse permutacije z začetkom doSedaj + [elt]
               # dodamo vsem
       return sorted(vse)

Uradna rešitev

def permutacijeV3(seznam, doSedaj = []):
    '''Vrne seznam vseh možnih permutacij iz elementov v elementi,
       ki imajo začetek enak permutaciji, ki so predstava doSedaj'''
    if seznam == []: # ni elementov, le prazna permutacija
        return [[]]
    if len(seznam) == len(doSedaj):# Če smo že na koncu
        return [doSedaj]
    vse = []
    for elt in seznam:
       if not elt in doSedaj: # če elt še lahko dodamo v doslej sestavljeno permutacijo
            permut = permutacijeV3(seznam, doSedaj + [elt]) # generiramo vse permutacije z začetkom doSedaj + [elt]
            vse += permut #dodamo vsem
    return sorted(vse)

4. podnaloga

Na osnovi ideje 4 smo zgradili osnutek metode permutacijeV4(seznam). Dopolni ga do delujočega!

    import itertools
    def permutacijeV4(seznam):
       '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
           permutacijo elementov iz seznama '''
       # uporabimo itertools
       # pazimo, ker ta vrne seznam naborov!
       return sorted(vse)

Uradna rešitev

import itertools
def permutacijeV4(seznam):        
    '''Vrne seznam seznamov, kjer posamezni seznam predstavlja
        permutacijo elementov iz seznama '''
    # uporabimo itertools
    permu = itertools.permutations(seznam)
    vse = list(map(list, permu)) # pazimo, ker ta vrne seznam naborov!
    return sorted(vse)